(function() {

	var codeGenerator = (typeof eval("(function () {})") == "function") ?
		function(code) {
			return code;
		} :
		function(code) {
			return "false || " + code;
		};

	// support string type only.
	var stringify = (typeof JSON !== "undefined" && JSON.stringify) ?
		function(s) {
			return JSON.stringify(s);
		} :
		(function() {
			// Implementation comes from JSON2 (http://www.json.org/js.html)

			var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;

			var meta = { // table of character substitutions
				'\b': '\\b',
				'\t': '\\t',
				'\n': '\\n',
				'\f': '\\f',
				'\r': '\\r',
				'"': '\\"',
				'\\': '\\\\'
			}

			return function(s) {
				// If the string contains no control characters, no quote characters, and no
				// backslash characters, then we can safely slap some quotes around it.
				// Otherwise we must also replace the offending characters with safe escape
				// sequences.

				escapable.lastIndex = 0;
				return escapable.test(s) ? '"' + s.replace(escapable, function(a) {
					var c = meta[a];
					return typeof c === 's' ? c :
						'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
				}) + '"' : '"' + s + '"';
			};
		})();

	// seed defined in global
	if(typeof __jscex__tempVarSeed === "undefined") {
		__jscex__tempVarSeed = 0;
	}

	var init = function(root) {

		if(root.modules["jit"]) {
			return;
		}

		function JscexTreeGenerator(binder) {
			this._binder = binder;
			this._root = null;
		}
		JscexTreeGenerator.prototype = {

			generate: function(ast) {

				var params = ast[2],
					statements = ast[3];

				this._root = {
					type: "delay",
					stmts: []
				};

				this._visitStatements(statements, this._root.stmts);

				return this._root;
			},

			_getBindInfo: function(stmt) {

				var type = stmt[0];
				if(type == "stat") {
					var expr = stmt[1];
					if(expr[0] == "call") {
						var callee = expr[1];
						if(callee[0] == "name" && callee[1] == this._binder && expr[2].length == 1) {
							return {
								expression: expr[2][0],
								argName: "",
								assignee: null
							};
						}
					} else if(expr[0] == "assign") {
						var assignee = expr[2];
						expr = expr[3];
						if(expr[0] == "call") {
							var callee = expr[1];
							if(callee[0] == "name" && callee[1] == this._binder && expr[2].length == 1) {
								return {
									expression: expr[2][0],
									argName: "$$_result_$$",
									assignee: assignee
								};
							}
						}
					}
				} else if(type == "var") {
					var defs = stmt[1];
					if(defs.length == 1) {
						var item = defs[0];
						var name = item[0];
						var expr = item[1];
						if(expr && expr[0] == "call") {
							var callee = expr[1];
							if(callee[0] == "name" && callee[1] == this._binder && expr[2].length == 1) {
								return {
									expression: expr[2][0],
									argName: name,
									assignee: null
								};
							}
						}
					}
				} else if(type == "return") {
					var expr = stmt[1];
					if(expr && expr[0] == "call") {
						var callee = expr[1];
						if(callee[0] == "name" && callee[1] == this._binder && expr[2].length == 1) {
							return {
								expression: expr[2][0],
								argName: "$$_result_$$",
								assignee: "return"
							};
						}
					}
				}

				return null;
			},

			_visitStatements: function(statements, stmts, index) {
				if(arguments.length <= 2) index = 0;

				if(index >= statements.length) {
					stmts.push({
						type: "normal"
					});
					return this;
				}

				var currStmt = statements[index];
				var bindInfo = this._getBindInfo(currStmt);

				if(bindInfo) {
					var bindStmt = {
						type: "bind",
						info: bindInfo
					};
					stmts.push(bindStmt);

					if(bindInfo.assignee != "return") {
						bindStmt.stmts = [];
						this._visitStatements(statements, bindStmt.stmts, index + 1);
					}

				} else {
					var type = currStmt[0];
					if(type == "return" || type == "break" || type == "continue" || type == "throw") {

						stmts.push({
							type: type,
							stmt: currStmt
						});

					} else if(type == "if" || type == "try" || type == "for" || type == "do" ||
						type == "while" || type == "switch" || type == "for-in") {

						var newStmt = this._visit(currStmt);

						if(newStmt.type == "raw") {
							stmts.push(newStmt);
							this._visitStatements(statements, stmts, index + 1);
						} else {
							var isLast = (index == statements.length - 1);
							if(isLast) {
								stmts.push(newStmt);
							} else {

								var combineStmt = {
									type: "combine",
									first: {
										type: "delay",
										stmts: [newStmt]
									},
									second: {
										type: "delay",
										stmts: []
									}
								};
								stmts.push(combineStmt);

								this._visitStatements(statements, combineStmt.second.stmts, index + 1);
							}
						}

					} else {

						stmts.push({
							type: "raw",
							stmt: currStmt
						});

						this._visitStatements(statements, stmts, index + 1);
					}
				}

				return this;
			},

			_visit: function(ast) {

				var type = ast[0];

				function throwUnsupportedError() {
					throw new Error('"' + type + '" is not currently supported.');
				}

				var visitor = this._visitors[type];

				if(visitor) {
					return visitor.call(this, ast);
				} else {
					throwUnsupportedError();
				}
			},

			_visitBody: function(ast, stmts) {
				if(ast[0] == "block") {
					this._visitStatements(ast[1], stmts);
				} else {
					this._visitStatements([ast], stmts);
				}
			},

			_noBinding: function(stmts) {
				switch(stmts[stmts.length - 1].type) {
					case "normal":
					case "return":
					case "break":
					case "throw":
					case "continue":
						return true;
				}

				return false;
			},

			_collectCaseStatements: function(cases, index) {
				var res = [];

				for(var i = index; i < cases.length; i++) {
					var rawStmts = cases[i][1];
					for(var j = 0; j < rawStmts.length; j++) {
						if(rawStmts[j][0] == "break") {
							return res
						}

						res.push(rawStmts[j]);
					}
				}

				return res;
			},

			_visitors: {

				"for": function(ast) {

					var bodyStmts = [];
					var body = ast[4];
					this._visitBody(body, bodyStmts);

					if(this._noBinding(bodyStmts)) {
						return {
							type: "raw",
							stmt: ast
						};
					}

					var delayStmt = {
						type: "delay",
						stmts: []
					};

					var setup = ast[1];
					if(setup) {
						delayStmt.stmts.push({
							type: "raw",
							stmt: setup
						});
					}

					var loopStmt = {
						type: "loop",
						bodyFirst: false,
						bodyStmt: {
							type: "delay",
							stmts: bodyStmts
						}
					};
					delayStmt.stmts.push(loopStmt);

					var condition = ast[2];
					if(condition) {
						loopStmt.condition = condition;
					}

					var update = ast[3];
					if(update) {
						loopStmt.update = update;
					}

					return delayStmt;
				},

				"for-in": function(ast) {

					var body = ast[4];

					var bodyStmts = [];
					this._visitBody(body, bodyStmts);

					if(this._noBinding(bodyStmts)) {
						return {
							type: "raw",
							stmt: ast
						};
					}

					var id = (__jscex__tempVarSeed++);
					var keysVar = "$$_keys_$$_" + id;
					var indexVar = "$$_index_$$_" + id;
					// var memVar = "$$_mem_$$_" + id;

					var delayStmt = {
						type: "delay",
						stmts: []
					};

					// var members = Jscex._forInKeys(obj);
					var keysAst = root.parse("var " + keysVar + " = Jscex._forInKeys(obj);")[1][0];
					keysAst[1][0][1][2][0] = ast[3]; // replace obj with real AST;
					delayStmt.stmts.push({
						type: "raw",
						stmt: keysAst
					});

					/*
					// var members = [];
					delayStmt.stmts.push({
					    type: "raw",
					    stmt: uglifyJS.parse("var " + membersVar + " = [];")[1][0]
					});
                    
					// for (var mem in obj) members.push(mem);
					var keysAst = uglifyJS.parse("for (var " + memVar +" in obj) " + membersVar + ".push(" + memVar + ");")[1][0];
					keysAst[3] = ast[3]; // replace the "obj" with real AST.
					delayStmt.stmts.push({ type : "raw", stmt: keysAst});
					*/

					// var index = 0;
					delayStmt.stmts.push({
						type: "raw",
						stmt: root.parse("var " + indexVar + " = 0;")[1][0]
					});

					// index < members.length
					var condition = root.parse(indexVar + " < " + keysVar + ".length")[1][0][1];

					// index++
					var update = root.parse(indexVar + "++")[1][0][1];

					var loopStmt = {
						type: "loop",
						bodyFirst: false,
						update: update,
						condition: condition,
						bodyStmt: {
							type: "delay",
							stmts: []
						}
					};
					delayStmt.stmts.push(loopStmt);

					var varName = ast[2][1]; // ast[2] == ["name", m]
					if(ast[1][0] == "var") {
						loopStmt.bodyStmt.stmts.push({
							type: "raw",
							stmt: root.parse("var " + varName + " = " + keysVar + "[" + indexVar + "];")[1][0]
						});
					} else {
						loopStmt.bodyStmt.stmts.push({
							type: "raw",
							stmt: root.parse(varName + " = " + keysVar + "[" + indexVar + "];")[1][0]
						});
					}

					this._visitBody(body, loopStmt.bodyStmt.stmts);

					return delayStmt;
				},

				"while": function(ast) {

					var bodyStmts = [];
					var body = ast[2];
					this._visitBody(body, bodyStmts);

					if(this._noBinding(bodyStmts)) {
						return {
							type: "raw",
							stmt: ast
						}
					}

					var loopStmt = {
						type: "loop",
						bodyFirst: false,
						bodyStmt: {
							type: "delay",
							stmts: bodyStmts
						}
					};

					var condition = ast[1];
					loopStmt.condition = condition;

					return loopStmt;
				},

				"do": function(ast) {

					var bodyStmts = [];
					var body = ast[2];
					this._visitBody(body, bodyStmts);

					if(this._noBinding(bodyStmts)) {
						return {
							type: "raw",
							stmt: ast
						};
					}

					var loopStmt = {
						type: "loop",
						bodyFirst: true,
						bodyStmt: {
							type: "delay",
							stmts: bodyStmts
						}
					};

					var condition = ast[1];
					loopStmt.condition = condition;

					return loopStmt;
				},

				"switch": function(ast) {
					var noBinding = true;

					var switchStmt = {
						type: "switch",
						item: ast[1],
						caseStmts: []
					};

					var cases = ast[2];
					for(var i = 0; i < cases.length; i++) {
						var caseStmt = {
							item: cases[i][0],
							stmts: []
						};
						switchStmt.caseStmts.push(caseStmt);

						var statements = this._collectCaseStatements(cases, i);
						this._visitStatements(statements, caseStmt.stmts);
						noBinding = noBinding && this._noBinding(caseStmt.stmts);
					}

					if(noBinding) {
						return {
							type: "raw",
							stmt: ast
						};
					} else {
						return switchStmt;
					}
				},

				"if": function(ast) {

					var noBinding = true;

					var ifStmt = {
						type: "if",
						conditionStmts: []
					};

					var currAst = ast;
					while(true) {
						var condition = currAst[1];
						var condStmt = {
							cond: condition,
							stmts: []
						};
						ifStmt.conditionStmts.push(condStmt);

						var thenPart = currAst[2];
						this._visitBody(thenPart, condStmt.stmts);

						noBinding = noBinding && this._noBinding(condStmt.stmts);

						var elsePart = currAst[3];
						if(elsePart && elsePart[0] == "if") {
							currAst = elsePart;
						} else {
							break;
						}
					}

					var elsePart = currAst[3];
					if(elsePart) {
						ifStmt.elseStmts = [];

						this._visitBody(elsePart, ifStmt.elseStmts);

						noBinding = noBinding && this._noBinding(ifStmt.elseStmts);
					}

					if(noBinding) {
						return {
							type: "raw",
							stmt: ast
						};
					} else {
						return ifStmt;
					}
				},

				"try": function(ast, stmts) {

					var bodyStmts = [];
					var bodyStatements = ast[1];
					this._visitStatements(bodyStatements, bodyStmts);

					var noBinding = this._noBinding(bodyStmts)

					var tryStmt = {
						type: "try",
						bodyStmt: {
							type: "delay",
							stmts: bodyStmts
						}
					};

					var catchClause = ast[2];
					if(catchClause) {
						var exVar = catchClause[0];
						tryStmt.exVar = exVar;
						tryStmt.catchStmts = [];

						this._visitStatements(catchClause[1], tryStmt.catchStmts);

						noBinding = noBinding && this._noBinding(tryStmt.catchStmts);
					}

					var finallyStatements = ast[3];
					if(finallyStatements) {
						tryStmt.finallyStmt = {
							type: "delay",
							stmts: []
						};

						this._visitStatements(finallyStatements, tryStmt.finallyStmt.stmts);

						noBinding = noBinding && this._noBinding(tryStmt.finallyStmt.stmts);
					}

					if(noBinding) {
						return {
							type: "raw",
							stmt: ast
						};
					} else {
						return tryStmt;
					}
				}
			}
		}

		function CodeGenerator(builderName, binder, indent) {
			this._builderName = builderName;
			this._binder = binder;
			this._normalMode = false;
			this._indent = indent;
			this._indentLevel = 0;
			this._builderVar = "$$_builder_$$_" + (__jscex__tempVarSeed++);
		}
		CodeGenerator.prototype = {
			_write: function(s) {
				this._buffer.push(s);
				return this;
			},

			_writeLine: function(s) {
				this._write(s)._write("\n");
				return this;
			},

			_writeIndents: function() {
				for(var i = 0; i < this._indent; i++) {
					this._write(" ");
				}

				for(var i = 0; i < this._indentLevel; i++) {
					this._write("    ");
				}
				return this;
			},

			generate: function(params, jscexAst) {
				this._buffer = [];

				this._writeLine("(function (" + params.join(", ") + ") {");
				this._indentLevel++;

				this._writeIndents()
					._writeLine("var " + this._builderVar + " = Jscex.builders[" + stringify(this._builderName) + "];");

				this._writeIndents()
					._writeLine("return " + this._builderVar + ".Start(this,");
				this._indentLevel++;

				this._pos = {};

				this._writeIndents()
					._visitJscex(jscexAst)
					._writeLine();
				this._indentLevel--;

				this._writeIndents()
					._writeLine(");");
				this._indentLevel--;

				this._writeIndents()
					._write("})");

				return this._buffer.join("");
			},

			_visitJscex: function(ast) {
				this._jscexVisitors[ast.type].call(this, ast);
				return this;
			},

			_visitRaw: function(ast) {
				var type = ast[0];

				function throwUnsupportedError() {
					throw new Error('"' + type + '" is not currently supported.');
				}

				var visitor = this._rawVisitors[type];

				if(visitor) {
					visitor.call(this, ast);
				} else {
					throwUnsupportedError();
				}

				return this;
			},

			_visitJscexStatements: function(statements) {
				for(var i = 0; i < statements.length; i++) {
					var stmt = statements[i];

					if(stmt.type == "raw" || stmt.type == "if" || stmt.type == "switch") {
						this._writeIndents()
							._visitJscex(stmt)._writeLine();
					} else if(stmt.type == "delay") {
						this._visitJscexStatements(stmt.stmts);
					} else {
						this._writeIndents()
							._write("return ")._visitJscex(stmt)._writeLine(";");
					}
				}
			},

			_visitRawStatements: function(statements) {
				for(var i = 0; i < statements.length; i++) {
					var s = statements[i];

					this._writeIndents()
						._visitRaw(s)._writeLine();

					switch(s[0]) {
						case "break":
						case "return":
						case "continue":
						case "throw":
							return;
					}
				}
			},

			_visitRawBody: function(body) {
				if(body[0] == "block") {
					this._visitRaw(body);
				} else {
					this._writeLine();
					this._indentLevel++;

					this._writeIndents()
						._visitRaw(body);
					this._indentLevel--;
				}

				return this;
			},

			_visitRawFunction: function(ast) {
				var funcName = ast[1] || "";
				var args = ast[2];
				var statements = ast[3];

				this._writeLine("function " + funcName + "(" + args.join(", ") + ") {")
				this._indentLevel++;

				var currInFunction = this._pos.inFunction;
				this._pos.inFunction = true;

				this._visitRawStatements(statements);
				this._indentLevel--;

				this._pos.inFunction = currInFunction;

				this._writeIndents()
					._write("}");
			},

			_jscexVisitors: {
				"delay": function(ast) {
					if(ast.stmts.length == 1) {
						var subStmt = ast.stmts[0];
						switch(subStmt.type) {
							case "delay":
							case "combine":
							case "normal":
							case "break":
							case "continue":
							case "loop":
							case "try":
								this._visitJscex(subStmt);
								return;
							case "return":
								if(!subStmt.stmt[1]) {
									this._visitJscex(subStmt);
									return;
								}
						}
					}

					this._writeLine(this._builderVar + ".Delay(function () {");
					this._indentLevel++;

					this._visitJscexStatements(ast.stmts);
					this._indentLevel--;

					this._writeIndents()
						._write("})");
				},

				"combine": function(ast) {
					this._writeLine(this._builderVar + ".Combine(");
					this._indentLevel++;

					this._writeIndents()
						._visitJscex(ast.first)._writeLine(",");
					this._writeIndents()
						._visitJscex(ast.second)._writeLine();
					this._indentLevel--;

					this._writeIndents()
						._write(")");
				},

				"loop": function(ast) {
					this._writeLine(this._builderVar + ".Loop(");
					this._indentLevel++;

					if(ast.condition) {
						this._writeIndents()
							._writeLine("function () {");
						this._indentLevel++;

						this._writeIndents()
							._write("return ")._visitRaw(ast.condition)._writeLine(";");
						this._indentLevel--;

						this._writeIndents()
							._writeLine("},");
					} else {
						this._writeIndents()._writeLine("null,");
					}

					if(ast.update) {
						this._writeIndents()
							._writeLine("function () {");
						this._indentLevel++;

						this._writeIndents()
							._visitRaw(ast.update)._writeLine(";");
						this._indentLevel--;

						this._writeIndents()
							._writeLine("},");
					} else {
						this._writeIndents()._writeLine("null,");
					}

					this._writeIndents()
						._visitJscex(ast.bodyStmt)._writeLine(",");

					this._writeIndents()
						._writeLine(ast.bodyFirst);
					this._indentLevel--;

					this._writeIndents()
						._write(")");
				},

				"raw": function(ast) {
					this._visitRaw(ast.stmt);
				},

				"bind": function(ast) {
					var info = ast.info;
					this._write(this._builderVar + ".Bind(")._visitRaw(info.expression)._writeLine(", function (" + info.argName + ") {");
					this._indentLevel++;

					if(info.assignee == "return") {
						this._writeIndents()
							._writeLine("return " + this._builderVar + ".Return(" + info.argName + ");");
					} else {
						if(info.assignee) {
							this._writeIndents()
								._visitRaw(info.assignee)._writeLine(" = " + info.argName + ";");
						}

						this._visitJscexStatements(ast.stmts);
					}
					this._indentLevel--;

					this._writeIndents()
						._write("})");
				},

				"if": function(ast) {

					for(var i = 0; i < ast.conditionStmts.length; i++) {
						var stmt = ast.conditionStmts[i];

						this._write("if (")._visitRaw(stmt.cond)._writeLine(") {");
						this._indentLevel++;

						this._visitJscexStatements(stmt.stmts);
						this._indentLevel--;

						this._writeIndents()
							._write("} else ");
					}

					this._writeLine("{");
					this._indentLevel++;

					if(ast.elseStmts) {
						this._visitJscexStatements(ast.elseStmts);
					} else {
						this._writeIndents()
							._writeLine("return " + this._builderVar + ".Normal();");
					}

					this._indentLevel--;

					this._writeIndents()
						._write("}");
				},

				"switch": function(ast) {
					this._write("switch (")._visitRaw(ast.item)._writeLine(") {");
					this._indentLevel++;

					for(var i = 0; i < ast.caseStmts.length; i++) {
						var caseStmt = ast.caseStmts[i];

						if(caseStmt.item) {
							this._writeIndents()
								._write("case ")._visitRaw(caseStmt.item)._writeLine(":");
						} else {
							this._writeIndents()._writeLine("default:");
						}
						this._indentLevel++;

						this._visitJscexStatements(caseStmt.stmts);
						this._indentLevel--;
					}

					this._writeIndents()
						._write("}");
				},

				"try": function(ast) {
					this._writeLine(this._builderVar + ".Try(");
					this._indentLevel++;

					this._writeIndents()
						._visitJscex(ast.bodyStmt)._writeLine(",");

					if(ast.catchStmts) {
						this._writeIndents()
							._writeLine("function (" + ast.exVar + ") {");
						this._indentLevel++;

						this._visitJscexStatements(ast.catchStmts);
						this._indentLevel--;

						this._writeIndents()
							._writeLine("},");
					} else {
						this._writeIndents()
							._writeLine("null,");
					}

					if(ast.finallyStmt) {
						this._writeIndents()
							._visitJscex(ast.finallyStmt)._writeLine();
					} else {
						this._writeIndents()
							._writeLine("null");
					}
					this._indentLevel--;

					this._writeIndents()
						._write(")");
				},

				"normal": function(ast) {
					this._write(this._builderVar + ".Normal()");
				},

				"throw": function(ast) {
					this._write(this._builderVar + ".Throw(")._visitRaw(ast.stmt[1])._write(")");
				},

				"break": function(ast) {
					this._write(this._builderVar + ".Break()");
				},

				"continue": function(ast) {
					this._write(this._builderVar + ".Continue()");
				},

				"return": function(ast) {
					this._write(this._builderVar + ".Return(");
					if(ast.stmt[1]) this._visitRaw(ast.stmt[1]);
					this._write(")");
				}
			},

			_rawVisitors: {
				"var": function(ast) {
					this._write("var ");

					var items = ast[1];
					for(var i = 0; i < items.length; i++) {
						this._write(items[i][0]);
						if(items[i].length > 1) {
							this._write(" = ")._visitRaw(items[i][1]);
						}
						if(i < items.length - 1) this._write(", ");
					}

					this._write(";");
				},

				"seq": function(ast) {
					for(var i = 1; i < ast.length; i++) {
						this._visitRaw(ast[i]);
						if(i < ast.length - 1) this._write(", ");
					}
				},

				"binary": function(ast) {
					var op = ast[1],
						left = ast[2],
						right = ast[3];

					function needBracket(item) {
						var type = item[0];
						return !(type == "num" || type == "name" || type == "dot");
					}

					if(needBracket(left)) {
						this._write("(")._visitRaw(left)._write(") ");
					} else {
						this._visitRaw(left)._write(" ");
					}

					this._write(op);

					if(needBracket(right)) {
						this._write(" (")._visitRaw(right)._write(")");
					} else {
						this._write(" ")._visitRaw(right);
					}
				},

				"sub": function(ast) {
					var prop = ast[1],
						index = ast[2];

					function needBracket() {
						return !(prop[0] == "name")
					}

					if(needBracket()) {
						this._write("(")._visitRaw(prop)._write(")[")._visitRaw(index)._write("]");
					} else {
						this._visitRaw(prop)._write("[")._visitRaw(index)._write("]");
					}
				},

				"unary-postfix": function(ast) {
					var op = ast[1];
					var item = ast[2];
					this._visitRaw(item)._write(op);
				},

				"unary-prefix": function(ast) {
					var op = ast[1];
					var item = ast[2];
					this._write(op);
					if(op == "typeof") {
						this._write("(")._visitRaw(item)._write(")");
					} else {
						this._visitRaw(item);
					}
				},

				"assign": function(ast) {
					var op = ast[1];
					var name = ast[2];
					var value = ast[3];

					this._visitRaw(name);
					if((typeof op) == "string") {
						this._write(" " + op + "= ");
					} else {
						this._write(" = ");
					}
					this._visitRaw(value);
				},

				"stat": function(ast) {
					this._visitRaw(ast[1])._write(";");
				},

				"dot": function(ast) {
					function needBracket() {
						var leftOp = ast[1][0];
						return !(leftOp == "dot" || leftOp == "name");
					}

					if(needBracket()) {
						this._write("(")._visitRaw(ast[1])._write(").")._write(ast[2]);
					} else {
						this._visitRaw(ast[1])._write(".")._write(ast[2]);
					}
				},

				"new": function(ast) {
					var ctor = ast[1];

					this._write("new ")._visitRaw(ctor)._write("(");

					var args = ast[2];
					for(var i = 0, len = args.length; i < len; i++) {
						this._visitRaw(args[i]);
						if(i < len - 1) this._write(", ");
					}

					this._write(")");
				},

				"call": function(ast) {

					if(_isJscexPattern(ast)) {
						var indent = this._indent + this._indentLevel * 4;
						var newCode = _compileJscexPattern(ast, indent);
						this._write(newCode);
					} else {

						var invalidBind = (ast[1][0] == "name") && (ast[1][1] == this._binder);
						if(invalidBind) {
							this._pos = {
								inFunction: true
							};
							this._buffer = [];
						}

						this._visitRaw(ast[1])._write("(");

						var args = ast[2];
						for(var i = 0; i < args.length; i++) {
							this._visitRaw(args[i]);
							if(i < args.length - 1) this._write(", ");
						}

						this._write(")");

						if(invalidBind) {
							throw("Invalid bind operation: " + this._buffer.join(""));
						}
					}
				},

				"name": function(ast) {
					this._write(ast[1]);
				},

				"object": function(ast) {
					var items = ast[1];
					if(items.length <= 0) {
						this._write("{ }");
					} else {
						this._writeLine("{");
						this._indentLevel++;

						for(var i = 0; i < items.length; i++) {
							this._writeIndents()
								._write(stringify(items[i][0]) + ": ")
								._visitRaw(items[i][1]);

							if(i < items.length - 1) {
								this._writeLine(",");
							} else {
								this._writeLine("");
							}
						}

						this._indentLevel--;
						this._writeIndents()._write("}");
					}
				},

				"array": function(ast) {
					this._write("[");

					var items = ast[1];
					for(var i = 0; i < items.length; i++) {
						this._visitRaw(items[i]);
						if(i < items.length - 1) this._write(", ");
					}

					this._write("]");
				},

				"num": function(ast) {
					this._write(ast[1]);
				},

				"regexp": function(ast) {
					this._write("/" + ast[1] + "/" + ast[2]);
				},

				"string": function(ast) {
					this._write(stringify(ast[1]));
				},

				"function": function(ast) {
					this._visitRawFunction(ast);
				},

				"defun": function(ast) {
					this._visitRawFunction(ast);
				},

				"return": function(ast) {
					if(this._pos.inFunction) {
						this._write("return");
						var value = ast[1];
						if(value) this._write(" ")._visitRaw(value);
						this._write(";");
					} else {
						this._write("return ")._visitJscex({
							type: "return",
							stmt: ast
						})._write(";");
					}
				},

				"for": function(ast) {
					this._write("for (");

					var setup = ast[1];
					if(setup) {
						this._visitRaw(setup);
						if(setup[0] != "var") {
							this._write("; ");
						} else {
							this._write(" ");
						}
					} else {
						this._write("; ");
					}

					var condition = ast[2];
					if(condition) this._visitRaw(condition);
					this._write("; ");

					var update = ast[3];
					if(update) this._visitRaw(update);
					this._write(") ");

					var currInLoop = this._pos.inLoop;
					this._pos.inLoop = true;

					var body = ast[4];
					this._visitRawBody(body);

					this._pos.inLoop = currInLoop;
				},

				"for-in": function(ast) {
					this._write("for (");

					var declare = ast[1];
					if(declare[0] == "var") { // declare == ["var", [["m"]]]
						this._write("var " + declare[1][0][0]);
					} else {
						this._visitRaw(declare);
					}

					this._write(" in ")._visitRaw(ast[3])._write(") ");

					var body = ast[4];
					this._visitRawBody(body);
				},

				"block": function(ast) {
					this._writeLine("{")
					this._indentLevel++;

					this._visitRawStatements(ast[1]);
					this._indentLevel--;

					this._writeIndents()
						._write("}");
				},

				"while": function(ast) {
					var condition = ast[1];
					var body = ast[2];

					var currInLoop = this._pos.inLoop
					this._pos.inLoop = true;

					this._write("while (")._visitRaw(condition)._write(") ")._visitRawBody(body);

					this._pos.inLoop = currInLoop;
				},

				"do": function(ast) {
					var condition = ast[1];
					var body = ast[2];

					var currInLoop = this._pos.inLoop;
					this._pos.inLoop = true;

					this._write("do ")._visitRawBody(body);

					this._pos.inLoop = currInLoop;

					if(body[0] == "block") {
						this._write(" ");
					} else {
						this._writeLine()._writeIndents();
					}

					this._write("while (")._visitRaw(condition)._write(");");
				},

				"if": function(ast) {
					var condition = ast[1];
					var thenPart = ast[2];

					this._write("if (")._visitRaw(condition)._write(") ")._visitRawBody(thenPart);

					var elsePart = ast[3];
					if(elsePart) {
						if(thenPart[0] == "block") {
							this._write(" ");
						} else {
							this._writeLine("")
								._writeIndents();
						}

						if(elsePart[0] == "if") {
							this._write("else ")._visitRaw(elsePart);
						} else {
							this._write("else ")._visitRawBody(elsePart);
						}
					}
				},

				"break": function(ast) {
					if(this._pos.inLoop || this._pos.inSwitch) {
						this._write("break;");
					} else {
						this._write("return ")._visitJscex({
							type: "break",
							stmt: ast
						})._write(";");
					}
				},

				"continue": function(ast) {
					if(this._pos.inLoop) {
						this._write("continue;");
					} else {
						this._write("return ")._visitJscex({
							type: "continue",
							stmt: ast
						})._write(";");
					}
				},

				"throw": function(ast) {
					var pos = this._pos;
					if(pos.inTry || pos.inFunction) {
						this._write("throw ")._visitRaw(ast[1])._write(";");
					} else {
						this._write("return ")._visitJscex({
							type: "throw",
							stmt: ast
						})._write(";");
					}
				},

				"conditional": function(ast) {
					this._write("(")._visitRaw(ast[1])._write(") ? (")._visitRaw(ast[2])._write(") : (")._visitRaw(ast[3])._write(")");
				},

				"try": function(ast) {

					this._writeLine("try {");
					this._indentLevel++;

					var currInTry = this._pos.inTry;
					this._pos.inTry = true;

					this._visitRawStatements(ast[1]);
					this._indentLevel--;

					this._pos.inTry = currInTry;

					var catchClause = ast[2];
					var finallyStatements = ast[3];

					if(catchClause) {
						this._writeIndents()
							._writeLine("} catch (" + catchClause[0] + ") {")
						this._indentLevel++;

						this._visitRawStatements(catchClause[1]);
						this._indentLevel--;
					}

					if(finallyStatements) {
						this._writeIndents()
							._writeLine("} finally {");
						this._indentLevel++;

						this._visitRawStatements(finallyStatements);
						this._indentLevel--;
					}

					this._writeIndents()
						._write("}");
				},

				"switch": function(ast) {
					this._write("switch (")._visitRaw(ast[1])._writeLine(") {");
					this._indentLevel++;

					var currInSwitch = this._pos.inSwitch;
					this._pos.inSwitch = true;

					var cases = ast[2];
					for(var i = 0; i < cases.length; i++) {
						var c = cases[i];
						this._writeIndents();

						if(c[0]) {
							this._write("case ")._visitRaw(c[0])._writeLine(":");
						} else {
							this._writeLine("default:");
						}
						this._indentLevel++;

						this._visitRawStatements(c[1]);
						this._indentLevel--;
					}
					this._indentLevel--;

					this._pos.inSwitch = currInSwitch;

					this._writeIndents()
						._write("}");
				}
			}
		}

		function _isJscexPattern(ast) {
			if(ast[0] != "call") return false;

			var evalName = ast[1];
			if(evalName[0] != "name" || evalName[1] != "eval") return false;

			var compileCall = ast[2][0];
			if(!compileCall || compileCall[0] != "call") return false;

			var compileMethod = compileCall[1];
			if(!compileMethod || compileMethod[0] != "dot" || compileMethod[2] != "compile") return false;

			var jscexName = compileMethod[1];
			if(!jscexName || jscexName[0] != "name" || jscexName[1] != "Jscex") return false;

			var builder = compileCall[2][0];
			if(!builder || builder[0] != "string") return false;

			var func = compileCall[2][1];
			if(!func || func[0] != "function") return false;

			return true;
		}

		function _compileJscexPattern(ast, indent) {

			var builderName = ast[2][0][2][0][1];
			var funcAst = ast[2][0][2][1];
			var binder = root.binders[builderName];

			var jscexTreeGenerator = new JscexTreeGenerator(binder);
			var jscexAst = jscexTreeGenerator.generate(funcAst);

			var codeGenerator = new CodeGenerator(builderName, binder, indent);
			var newCode = codeGenerator.generate(funcAst[2], jscexAst);

			return newCode;
		}

		function compile(builderName, func) {

			var funcCode = func.toString();
			var evalCode = "eval(Jscex.compile(" + stringify(builderName) + ", " + funcCode + "))"
			var evalCodeAst = root.parse(evalCode);

			// [ "toplevel", [ [ "stat", [ "call", ... ] ] ] ]
			var evalAst = evalCodeAst[1][0][1];
			var newCode = _compileJscexPattern(evalAst, 0);

			root.logger.debug(funcCode + "\n\n>>>\n\n" + newCode);

			return codeGenerator(newCode);
		};

		root.compile = compile;

		root.modules["jit"] = true;
	}

	var isCommonJS = (typeof require !== "undefined" && typeof module !== "undefined" && module.exports);
	var isAmd = (typeof define !== "undefined" && define.amd);

	if(isCommonJS) {
		module.exports.init = function(root) {
			if(!root.modules["parser"]) {
				require("./jscex-parser").init(root);
			};

			init(root);
		}
	} else if(isAmd) {
		define("jscex-jit", ["jscex-parser"], function(parser) {
			return {
				init: function(root) {
					if(!root.modules["parser"]) {
						parser.init(root);
					}

					init(root);
				}
			};
		});
	} else {
		if(typeof Jscex === "undefined") {
			throw new Error('Missing root object, please load "jscex" module first.');
		}

		if(!Jscex.modules["parser"]) {
			throw new Error('Missing essential components, please initialize "parser" module first.');
		}

		init(Jscex);
	}

})();